package com.neo.test.iostream;

import com.neo.test.pojo.User;
import org.junit.Test;

import java.io.*;

/**
 * @ClassName: IODemo
 * @Title: demo
 * @Package: com.neo.test.iostream
 * @Description:
 * @Author: Kisen
 * @Date: 2021/8/22 23:40
 */
public class IODemo {

    // ======字节流=====
    // =====文件节点流FileInputStream、FileOutputStream======

    @Test
    public void testNoArgsReadFile() throws IOException {
        File file = new File("D:/三国/诸葛亮.txt");
        FileInputStream fileInputStream = new FileInputStream(file);

        // 核心代码
        int b;
        while ((b = fileInputStream.read()) != -1) {
            System.out.print((char) b);
        }

        // 输出结果 abcdefg
    }

    @Test
    public void testReadFile() throws IOException {
        File file = new File("D:/三国/诸葛亮.txt");
        FileInputStream fileInputStream = new FileInputStream(file);

        // 核心代码
        byte[] data = new byte[2];
        while (fileInputStream.read(data) != -1) {
            System.out.println(new String(data));
        }

        // 输出结果:
        // ab
        // cd
        // ef
        // gf
        // 说明：由于最后一次读取时，只读取一个字节 g ，
        // 数组中还是上次的数据ef，只替换了g，所以最后输出了gd
    }

    /**
     * 使用FileInputStream读取的正确姿势
     *
     * @throws IOException
     */
    @Test
    public void testFormalReadFile() throws IOException {
        File file = new File("D:/三国/诸葛亮.txt");
        FileInputStream fileInputStream = new FileInputStream(file);

        // 核心代码
        byte[] data = new byte[2];
        int len;
        while ((len = fileInputStream.read(data)) != -1) {
            // len 为每次读取的有效的字节个数
            System.out.println(new String(data, 0, len));
        }
        // 输出结果：
        // ab
        // cd
        // ef
        // g
    }

    @Test
    public void testFileOutputStream() throws FileNotFoundException {
        File file = new File("D:/三国/赵云.txt");
        FileOutputStream fos = new FileOutputStream(file);
        FileOutputStream fos1 = new FileOutputStream("D:/三国/司马懿.txt");
        // 上述代码执行完后，赵云.txt和司马懿.txt都会自动创建出来
    }

    @Test
    public void testWriteFile() throws IOException {
        FileOutputStream fos = new FileOutputStream("D:/三国/司马懿.txt");
        fos.write(96);
        fos.write(97);
        fos.write(98);
        // 文件内容为 `ab

        FileOutputStream fos2 = new FileOutputStream("D:/三国/赵云.txt");
        fos2.write("三国赵云".getBytes());
        // 文件内容为 三国赵云
    }

    @Test
    public void testWriteFileAppend() throws IOException {
        FileOutputStream fos = new FileOutputStream("D:/三国/赵云.txt", true);
        fos.write("有万夫不当之勇".getBytes());
        fos.close();
        // 文件内容为 三国赵云有万夫不当之勇
    }

    // ======内存节点流======
    // ByteArrayInputStream是从内存的字节数组中读取数据
    // ByteArrayOutputStream是向内存字节数组中写数据，内部维护了一个数组

    @Test
    public void testByteArrayStream() {
        ByteArrayInputStream bis = new ByteArrayInputStream("data".getBytes());
        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        int len = 0;
        while ((len = bis.read()) != -1) {
            bos.write(len);
        }
        // 输出data
        System.out.println(new String(bos.toByteArray()));
    }

    // =======字符流========
    // 字符流封装了更加适合操作文本字符的方法

    // =======文件节点流FileReader、FileWriter======

    @Test
    public void testNoArgsReadFileByCharacterStream() throws IOException {
        FileReader fileReader = new FileReader("D:/三国/赵云.txt");
        int b;
        while ((b = fileReader.read()) != -1) {
            System.out.println((char) b);
        }
    }

    @Test
    public void testReadFileByCharacterStream() throws IOException {
        FileReader fileReader = new FileReader("D:/三国/赵云.txt");
        int len;
        char[] data = new char[2];
        while ((len = fileReader.read(data)) != -1) {
            System.out.println(new String(data, 0, len));
        }
        // 两个字符两个字符依次读取
    }

    /**
     * 如果不执行close()或者flush()方法，数据只是保存到缓冲区，
     * 不会保存到文件。这点和与FileOutputStream不同，原因见 字节流和字符流的共同点章节
     *
     * @throws IOException
     */
    @Test
    public void testWriteFileByCharacterStream() throws IOException {
        FileWriter fileWriter = new FileWriter("D:/三国/孙权.txt");
        fileWriter.write(97);
        fileWriter.write('b');
        fileWriter.write('C');
        fileWriter.write("权");
        fileWriter.write("力");
    }

    // ======内存节点流======
    // 字符流也有对应的内存节点流，常用的有StringWriter和CharArrayWriter
    // StringWriter是向内部的StringBuffer对象写数据
    // CharArrayWriter是向内部的char数组写数据

    @Test
    public void testStringWriter() {
        StringWriter sw = new StringWriter();
        sw.write("hello");

        StringBuffer buffer = sw.getBuffer();
        // 输出hello
        System.out.println(buffer.toString());
    }

    @Test
    public void testCharArrayWriter() throws IOException {
        CharArrayWriter caw = new CharArrayWriter();
        caw.write("hello");
        char[] chars = caw.toCharArray();
        for (char c : chars) {
            // 输出了 h e l l o
            System.out.println(c);
        }
    }

    // 字节流和字符流的共同点:
    // OutputStream、Reader、Writer都实现了Flushable接口，Flushable接口有flush()方法


    // 处理流
    // 处理流是对节点流在功能上、性能上的增强
    // 节点流，都是直接使用操作系统底层方法读取硬盘中的数据，缓冲流是处理流的一种实现，
    // 增强了节点流的性能，为了提高效率，缓冲流类在初始化对象的时候，内部有一个缓冲数组，
    // 一次性从底层流中读取数据到数组中，程序中执行read()或者read(byte[])的时候，
    // 就直接从内存数组中读取数据。

    // =========字节缓冲流：BufferedInputStream、BufferedOutputStream=======

    @Test
    public void testBaseStreamReadDataByByte() throws IOException {
        long start = System.currentTimeMillis();
        FileInputStream fis = new FileInputStream("D:/三国/视频.mp4");
        FileOutputStream fos = new FileOutputStream("D:/三国/拷贝.mp4");

        int data;
        while ((data = fis.read()) != -1) {
            fos.write(data);
        }
        System.out.println(String.format("拷贝电影耗时：%d ms", System.currentTimeMillis() - start));
        // 五分钟还没拷好，关闭程序了...
    }

    @Test
    public void testBaseStreamReadDataByBytes() throws IOException {
        long start = System.currentTimeMillis();
        FileInputStream fis = new FileInputStream("D:/三国/视频.mp4");
        FileOutputStream fos = new FileOutputStream("D:/三国/拷贝.mp4");

        int len;
        byte[] data = new byte[1024 * 1024 * 1024];
        while ((len = fis.read(data)) != -1) {
            fos.write(data, 0, len);
        }
        System.out.println(String.format("拷贝电影耗时：%d ms", System.currentTimeMillis() - start));
        // 拷贝电影耗时：2805ms
    }

    @Test
    public void testBufferedStreamReadDataByByte() throws IOException {
        long start = System.currentTimeMillis();
        BufferedInputStream fis = new BufferedInputStream(new FileInputStream("D:/三国/视频.mp4"));
        BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream("D:/三国/拷贝.mp4"));

        int data;
        while ((data = fis.read()) != -1) {
            fos.write(data);
        }
        System.out.println(String.format("拷贝电影耗时：%d ms", System.currentTimeMillis() - start));
        // 拷贝电影耗时：37592ms
    }

    @Test
    public void testBufferedStreamReadDataByBytes() throws IOException {
        long start = System.currentTimeMillis();
        BufferedInputStream fis = new BufferedInputStream(new FileInputStream("D:/三国/视频.mp4"));
        BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream("D:/三国/拷贝.mp4"));

        int len;
        byte[] data = new byte[8 * 1024];
        while ((len = fis.read(data)) != -1) {
            fos.write(data, 0, len);
        }
        System.out.println(String.format("拷贝电影耗时：%d ms", System.currentTimeMillis() - start));
        // 拷贝电影耗时：1536ms
    }

    // 由上述四个例子可以得出结论，缓冲流读取数据比普通流读取数据快很多！


    // =========字符缓冲流：BufferedReader、BufferedWriter======
    // 字符缓冲流的特有方法
    // BufferedReader：readLine()
    // BufferedWriter：newLine()

    @Test
    public void testBufferedStreamByCharacterStream() throws IOException {
        // 创建流对象
        BufferedReader br = new BufferedReader(new FileReader("D:/三国/赵云.txt"));
        BufferedWriter bw = new BufferedWriter(new FileWriter("D:/三国/赵子龙.txt"));
        String line = null;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
            bw.write(line);
            bw.newLine();
        }
        // 结果：
        // 我乃常山赵子龙
        // 于万军从中，取上将首级
    }


    // ========缓冲流的正确姿势========

    @Test
    public void testFormalBufferedStream() {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            bis = new BufferedInputStream(new FileInputStream("D:/三国/视频.mp4"));
            bos = new BufferedOutputStream(new FileOutputStream("D:/三国/拷贝.mp4"));
            int len;
            // 一次传输8M的文件，实际测试这里传输的大小并不影响传输的速度
            byte[] data = new byte[8 * 1024];
            while ((len = bis.read(data)) != -1) {
                bos.write(data, 0, len);
            }
        } catch (IOException e) {
            System.out.println(String.format("error: %s", e));
        } finally {
            // finally块中关闭流，确保资源一定被关闭
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    System.out.println(String.format("error: %s", e));
                }
            }
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException e) {
                    System.out.println(String.format("error: %s", e));
                }
            }
        }

    }


    // =======转换流=======
    // 字符编码与字符集

    @Test
    public void testInputStreamReader() throws IOException {
        // 创建流对象，默认UTF8编码
        InputStreamReader isr = new InputStreamReader(new FileInputStream("D:/三国/utf8.txt"));
        // 创建流对象，指定GBK编码
        InputStreamReader isr2 = new InputStreamReader(new FileInputStream("D:/三国/utf8.txt"), "GBK");

        int read;
        while ((read = isr.read()) != -1) {
            System.out.println((char) read);
        }

        while ((read = isr2.read()) != -1) {
            System.out.println((char) read);
        }

        // 输出结果：
        // 你好
        // 浣犲ソ
    }

    @Test
    public void testOutputStreamWriter() throws IOException {
        // 创建流对象，默认UTF8编码
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:/三国/黄忠.txt"));
        osw.write("你好"); // 保存为6个字节
        osw.close();

        // 创建流对象，指定GBK编码
        OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream("D:/三国/马超.txt"), "GBK");
        osw2.write("你好"); // 保存为4个字节
        osw2.close();
    }

    // =======对象流========

    // 对象序列化

    @Test
    public void testObjectOutputStream() throws IOException {
        // 将对象输出到文件的核心代码
        User user = new User("马超", 20);
        // 创建序列化流对象
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:/三国/马超.txt"));
        // 写出对象
        out.writeObject(user);
    }

    // 反序列化

    @Test
    public void testObjectInputStream() throws IOException, ClassNotFoundException {
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:/三国/马超.txt"));
        // 强转为user
        User user = (User) in.readObject();
        System.out.println(user);
        // 输出内容
        // User{name='马超', age=20}
    }

    // ======对象和字节数组的转换=========

    @Test
    public void testObj2ByteArray() throws IOException, ClassNotFoundException {
        User user = new User("马超", 20);
        byte[] data = t1(user);
        User user2 = t2(data);
        System.out.println(user2);
    }

    public static <T> byte[] t1(T t) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(t);
        return bos.toByteArray();
    }

    public static <T> T t2(byte[] data) throws IOException, ClassNotFoundException {
        ByteArrayInputStream bis = new ByteArrayInputStream(data);
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (T) ois.readObject();
    }
}
